Estadísticos, operaciones múltiples y resúmenes

Unidad 5

Estadísticos


Las funciones que calculan medidas de tendencia central, de dispersión y de posición, que son parte integral del lenguaje R, como mean(), sd() o max(), se aplican sobre vectores o variables de un dataframes (internamente funcionan igual), utilizando estructuras de tidyverse como el summarise().


Asociadas al group_by(), o a la inclusión del argumento .by = dentro de summarise(), se obtiene estos resultados estratificados por categorías de variables cualitativas (las usamos para agrupar).


Esta tarea conlleva escribir una línea de código por cada variable.

Operaciones simultáneas por columnas


La filosofía de trabajo de tidyverse se plantea nunca copiar y pegar más de dos veces el código escrito, pero cuando necesitamos realizar la misma operación en un conjunto de variables simultáneamente nos encontramos con este problema.

La solución, ofrecida dentro de dplyr, es un andamiaje que permite aplicar funciones y expresiones a varias columnas simultáneamente.

Es una forma de iteración, donde se repite la misma acción en diferentes objetos. En este caso los objetos serán columnas (variables) de la tabla de datos.

Las operaciones simultáneas pueden darse como transformación (dentro de un mutate()) o de resumen (dentro de un summarise())

Operaciones simultáneas por columnas


Creación de múltiples columnas con mutate()

Resumiendo múltiples columnas con summarise()

Función across()


La función across() es la encargada de dar soporte a estas operaciones múltiples (dplyr >= 1.0.0).

across(.cols,  .fns,  ...,  .names)


.cols = columnas a transformar

.fns = función o funciones para aplicar a cada columna de .cols

... = argumentos adicionales de las funciones especificadas anteriormente (ejemplo: na.rm = T)

.names = nombres de las columnas de salida. Aquí, {.col} es un marcador especial al que se le puede agregar el sufijo deseado.

Resúmenes múltiples


Tomemos la siguiente tabla de datos ficticios (mostramos las primeras 4 observaciones):

# A tibble: 4 × 4
        a     b      c      d
    <dbl> <dbl>  <dbl>  <dbl>
1 -0.560  1.22  -1.07   0.426
2 -0.230  0.360 -0.218 -0.295
3  1.56   0.401 -1.03   0.895
4  0.0705 0.111 -0.729  0.878


Supongamos que queremos calcular la media de cada variable…

Resúmenes múltiples

Podríamos hacerlo repitiendo para cada variable

datos |> summarise(
  a = mean(a),
  b = mean(b),
  c = mean(c),
  d = mean(d),
)
# A tibble: 1 × 4
       a     b      c     d
   <dbl> <dbl>  <dbl> <dbl>
1 0.0746 0.209 -0.425 0.322


Pero esto rompe la regla general que buscamos de nunca copiar y pegar más de dos veces…

Resúmenes múltiples

Para solucionarlo aplicamos across() y realizamos el resumen simultáneo en una sola línea.

datos |> summarise(
  across(.cols = a:d, .fns = mean),
)
# A tibble: 1 × 4
       a     b      c     d
   <dbl> <dbl>  <dbl> <dbl>
1 0.0746 0.209 -0.425 0.322


Nótese que el primer argumento es el rango de nombres de variables y el segundo la función que aplicamos a todas ellas (nombres de funciones sin paréntesis).

Seleccionar variables (.cols)


El primer argumento de across() responde de la misma forma que la función select() y aplican también las funciones ayudantes de selección.

names(datos)
[1] "grupo" "a"     "b"     "c"     "d"    
datos |> 
  group_by(grupo) |> 
  summarize(across(everything(), mean))
# A tibble: 2 × 5
  grupo      a       b      c      d
  <int>  <dbl>   <dbl>  <dbl>  <dbl>
1     1 -0.191 -0.331  -0.485 -0.357
2     2  0.132 -0.0552  0.421  0.239

Recordamos a las funciones ayudantes de selección


  • everything(): coincide con todas las variables.

  • group_cols(): seleccione todas las columnas de agrupación.

  • starts_with(): comienza con un prefijo.

  • ends_with(): termina con un sufijo.

  • contains(): contiene una cadena literal.

  • matches(): coincide con una expresión regular.

  • num_range(): coincide con un rango numérico como x01, x02, x03.

  • all_of(): coincide con nombres de variables en un vector de caracteres. Todos los nombres deben estar presentes; de lo contrario, se generará un error de fuera de límites.

  • any_of(): igual que all_of(), excepto que no se genera ningún error para los nombres que no existen.

  • where(): aplica una función a todas las variables y selecciona aquellas para las cuales la función regresa TRUE.

Expresiones de selección


El argumento .cols también puede recibir construcciones booleanas utilizando los operadores conocidos como ! (negación) y conectores lógicos como & (AND) y | (OR) entre las funciones ayudantes de selección.


Por ejemplo:

.cols = !where(is.numeric) & starts_with("a")


Selecciona todas las columnas no numéricas, cuyo nombre comienza con “a”.

Agregar argumentos a las funciones


Hasta ahora vimos el ejemplo de aplicar una función simple como mean() a un grupo de variables.


Que sucede si entre los datos de esas variables hay valores NA?

Vamos a necesitar incorporar el argumento na.rm = TRUE a la función.


Donde lo hacemos dentro de un across()?

Agregar argumentos a las funciones


Supongamos que tenemos estos datos (mostramos algunas observaciones):

# A tibble: 4 × 4
       a      b      c      d
   <dbl>  <dbl>  <dbl>  <dbl>
1  1.56  -1.27  NA     -0.473
2 -0.560 NA     -1.05  -1.07 
3 -0.230  1.22   0.238 -0.218
4 NA     -0.446  1.29  -1.03 


Vemos algunos valores NA entre las observaciones.

Agregar argumentos a las funciones


Si aplicamos el mismo código de across() anterior tendríamos como resultado:

datos_na |> 
  summarise(
    across(a:d, mean)
  )
# A tibble: 1 × 4
      a     b     c      d
  <dbl> <dbl> <dbl>  <dbl>
1    NA    NA    NA -0.703


Sería bueno que le pasaramos na.rm = TRUE a la función mean().

Agregar argumentos a las funciones

Existen dos formas sintácticas de hacerlo.

  • Una función estilo-purrr (tidyverse): ~ mean(.x, na.rm = TRUE)

  • Una función anónima (base): function(x) mean(x, na.rm = TRUE) ; o mejor en su forma de atajo: \(x) mean(x, na.rm = TRUE)

datos_na |> 
  summarise(
    across(a:d, \(x) mean(x, na.rm = TRUE))
  )
# A tibble: 1 × 4
      a      b     c      d
  <dbl>  <dbl> <dbl>  <dbl>
1 0.210 -0.293 0.161 -0.703

Múltiples funciones

Para incorporar más de una función dentro de across() debemos incluirlas dentro de una lista [list()]

datos_na |> 
  summarise(
    across(a:d, list(
      media = \(x) mean(x, na.rm = TRUE),
      n_na = \(x) sum(is.na(x))))
  )
# A tibble: 1 × 8
  a_media a_n_na b_media b_n_na c_media c_n_na d_media d_n_na
    <dbl>  <int>   <dbl>  <int>   <dbl>  <int>   <dbl>  <int>
1   0.210      1  -0.293      1   0.161      2  -0.703      0

La lista contiene cada función a aplicar, bajo nombres definidos.

Cambiar nombres de resultados


Observemos que los nombres de las variables resultado se componen del nombre de la columna, un guión bajo y el nombre definido de la función aplicada, para distinguir entre las múltiples funciones del across().

La estructura de estos nombres se pueden modificar con el argumento .names de across().

Los marcadores especiales para el nombre de columna es {.col} y para el nombre de la función definida es {.fn}.

Cambiar nombres de resultados


Por ejemplo, podríamos invertir el orden predeterminado de los nombres del resumen.

datos_na |> 
  summarise(
    across(a:d, list(
      media = \(x) mean(x, na.rm = TRUE),
      n_na = \(x) sum(is.na(x))),
      .names = "{.fn}_{.col}")
  )
# A tibble: 1 × 8
  media_a n_na_a media_b n_na_b media_c n_na_c media_d n_na_d
    <dbl>  <int>   <dbl>  <int>   <dbl>  <int>   <dbl>  <int>
1   0.210      1  -0.293      1   0.161      2  -0.703      0

Transformación de tipos de datos


Hasta ahora vimos como funciona la función across() dentro de un resumen (summarise) pero al comienzo también dijimos que se puede utilizar para transformaciones masivas de datos.


Para lograr esto la función se vincula con mutate() modificando las variables originales o bien creando nuevas variables si cambiamos su nombre con .names.

Transformación de tipos de datos


Aplicamos la función coalesce() para convertir los valores NA en ceros, transformando las variables originales.

datos_na |> 
  mutate(
    across(a:d, \(x) coalesce(x, 0))
  )
# A tibble: 5 × 4
        a      b      c      d
    <dbl>  <dbl>  <dbl>  <dbl>
1  1.56   -1.27   0     -0.473
2 -0.560   0     -1.05  -1.07 
3 -0.230   1.22   0.238 -0.218
4  0      -0.446  1.29  -1.03 
5  0.0705 -0.687  0     -0.729

Transformación de tipos de datos

Hacemos lo mismo pero cambiamos los nombres de las variables de salida del mutate() que van a coexistir con las originales.

datos_na |> 
  mutate(
    across(a:d, \(x) coalesce(x, 0),
      .names = "{.col}_na_cero")
  )
# A tibble: 5 × 8
        a      b      c      d a_na_cero b_na_cero c_na_cero d_na_cero
    <dbl>  <dbl>  <dbl>  <dbl>     <dbl>     <dbl>     <dbl>     <dbl>
1  1.56   -1.27  NA     -0.473    1.56      -1.27      0        -0.473
2 -0.560  NA     -1.05  -1.07    -0.560      0        -1.05     -1.07 
3 -0.230   1.22   0.238 -0.218   -0.230      1.22      0.238    -0.218
4 NA      -0.446  1.29  -1.03     0         -0.446     1.29     -1.03 
5  0.0705 -0.687 NA     -0.729    0.0705    -0.687     0        -0.729

Filtros


En el caso de iteraciones similares para incluir dentro de la función filter() el paquete dplyr propone dos funciones específicas: if_any() e if_all().


En el primer caso, la función enmascara una repetición de OR lógicos y en la segunda una secuencia de AND lógicos.

Filtros

datos_na |> filter(if_any(a:d, is.na))
# A tibble: 4 × 4
        a      b     c      d
    <dbl>  <dbl> <dbl>  <dbl>
1  1.56   -1.27  NA    -0.473
2 -0.560  NA     -1.05 -1.07 
3 NA      -0.446  1.29 -1.03 
4  0.0705 -0.687 NA    -0.729

Es lo mismo que filter(is.na(a) | is.na(b) | is.na(c) | is.na(d))

datos_na |> filter(if_all(a:d, is.na))
# A tibble: 0 × 4
# ℹ 4 variables: a <dbl>, b <dbl>, c <dbl>, d <dbl>

Es lo mismo que filter(is.na(a) & is.na(b) & is.na(c) & is.na(d))

Filtros


Las dos funciones de filtro trabajan con el mismo esquema que across(), por lo tanto se le puede aplicar una función o expresión de condición (debe devolver TRUE o FALSE)


datos  |> filter(if_all(a:d, \(x) x > -0.5 & x < 1))
# A tibble: 2 × 5
  grupo     a     b      c       d
  <int> <dbl> <dbl>  <dbl>   <dbl>
1     2 0.780 0.124  0.922 0.181  
2     2 0.253 0.380 -0.491 0.00576

Paquete rstatix


El paquete rstatix simplifica el proceso de realizar análisis estadísticos complejos.

  • sintaxis intuitiva y basada en el paradigma de la “gramática de datos” de tidyverse
  • permite realizar pruebas estadísticas comunes, como t-tests, ANOVA, y correlaciones, de manera rápida y eficiente
  • diseño que facilita la integración de resultados en flujos de trabajo de análisis reproducibles, asegurando que las conclusiones derivadas de los datos sean robustas y transparentes.

Paquete rstatix

  • Muchas de sus funciones son espejos de las funciones estadísticas de R base pero compatibles con tidyverse, por lo que siempre devuelven los resultados en dataframes
library(rstatix)

datos |> 
  t_test(Edad ~ Sexo)
# A tibble: 1 × 8
  .y.   group1 group2    n1    n2 statistic    df     p
* <chr> <chr>  <chr>  <int> <int>     <dbl> <dbl> <dbl>
1 Edad  Mujer  Varon     29    33    -0.871  59.6 0.387

Lo que nos permite conectar el resultado mediante tuberías hacia otras funciones del tidyverse, como select() o filter().

Paquete rstatix


Tiene dos funciones de resumenes para variables cuantitativas y cualitativas:

datos |> get_summary_stats(Edad, type = "mean_sd")
# A tibble: 1 × 4
  variable     n  mean    sd
  <fct>    <dbl> <dbl> <dbl>
1 Edad        62  58.6  24.5
datos |> get_summary_stats(Edad, type = "median_iqr")
# A tibble: 1 × 4
  variable     n median   iqr
  <fct>    <dbl>  <dbl> <dbl>
1 Edad        62   62.5    40

Paquete rstatix

datos |> group_by(Sexo) |> 
  get_summary_stats(Edad, type = "common")
# A tibble: 2 × 11
  Sexo  variable     n   min   max median   iqr  mean    sd    se    ci
  <chr> <fct>    <dbl> <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Mujer Edad        29    18    93     57    39  55.7  23.9  4.44  9.10
2 Varon Edad        33    19    99     63    41  61.1  25.1  4.37  8.90
datos |> freq_table(Sexo)
# A tibble: 2 × 3
  Sexo      n  prop
  <chr> <int> <dbl>
1 Mujer    31  46.3
2 Varon    36  53.7

Paquete janitor


El paquete janitor tiene numerosas funciones de las cuales vamos a destacar la familia tabyl.


La función tabyl() permite crear tablas de frecuencia de una variable o tablas de contingencia para dos o más variables categóricas.


El paquete además tiene otras funciones que se enlazan con tabyl para personalizar los resultados en estas tablas (por ejemplo, agregar totales o representar frecuencias realtivas como porcentajes)

Paquete janitor

Se puede personalizar la tabla conectando por tuberías el agregado de otras funciones del paquete.

library(janitor)
datos  |>   
  tabyl(Sexo) |>  
  adorn_totals(where = "row") |>  
  adorn_pct_formatting(digits = 2) 
  Sexo  n percent
 Mujer 31  46.27%
 Varon 36  53.73%
 Total 67 100.00%

Incorporamos totales con adorn_totals() y configuramos los porcentajes con adorn_pct_formatting().

gtsummary


Este paquete permite crear tablas de resumen elegantes y personalizables que destacan las estadísticas descriptivas, resultados de modelos, y comparaciones de grupos.

Estas tablas son esenciales para reportes, publicaciones y presentaciones, asegurando que la información clave sea fácilmente comprensible.

Las salidas buscan producir estéticas compatibles con las tablas que se envían a publicar en la mayoría de las revistas científicas, así como también en otras publicaciones similares.

gtsummary


Un ejemplo de una tabla descriptiva con variables cuanti y cuali estratificada por Sexo.

flextable


Este paquete es similar a otras librerías como gt, huxtable, kableExtra, etc, que transforman las tablas y dataframes de R en salidas elegantes.


La documentación oficial dice “crear tablas flexibles y altamente personalizables en formatos de Microsoft Word, PowerPoint y HTML”.


La elección de este paquete sobre las otras opciones tiene que ver con la amplitud de opciones en la compatibilidad de los formatos de salida.

Las funciones del paquete se integran facilmente con Quarto

flextable


Una tablita que creamos anteriormente con freq_table() de rstatix fue:

datos |> freq_table(Sexo)
# A tibble: 2 × 3
  Sexo      n  prop
  <chr> <int> <dbl>
1 Mujer    31  46.3
2 Varon    36  53.7


La salida tradicional de consola se visualiza estéticamente fea.

flextable

Con flextable podemos configurar esa misma tabla, para mejorar su presentación:

library(flextable)
datos |> freq_table(Sexo) |> 
  flextable() |> 
  fontsize(size = 30, part = "all") |> 
  align(align = "center", part = "all") |> 
  line_spacing(space = 2, part = "all") |> 
  padding(padding = 6, part = "header") |> 
  set_header_labels(n = "Frecuencia", prop = "%") |> 
  theme_zebra()

Sexo

Frecuencia

%

Mujer

31

46.3

Varon

36

53.7